#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include<stdlib.h>

#include "server.h"

static char* ok_response = 
    "HTTP/1.1 200 OK\n"
    "Content-Type: text/html\n"
    "\n";

static char* bad_request_response = 
    "HTTP/1.0 400 Bad Request\n"
    "Content-type: text/html\n"
    "\n"
    "<html>\n"
    " <body>\n"
    " <h1>Bad Request</h1>\n"
    " <p>This server did not understand your request.</p>\n"
    " </body>\n"
    "</html>\n";

static char* not_found_response_template = 
    "HTTP/1.0 404 Not Found\n"
    "Content-type: text/html\n"
    "\n"
    "<html>\n"
    " <body>\n"
    " <h1>Not Found</h1>\n"
    " <p>The requested URL %s was not found on this server.</p>\n"
    " </body>\n"
    "</html>\n";

static char* bad_method_response_template =
    "HTTP/1.0 501 Method Not Implemented\n"
    "Content-type: text/html\n"
    "\n"
    "<html>\n"
    " <body>\n"
    " <h1>Method Not Implemented</h1>\n"
    " <p>The method %s is not implemented by this server.</p>\n"
    " </body>\n"
    "</html>\n";

const size_t BUFFER_SIZE = 256;

/* Handler for SIGCHLD, to clean up child processes that have
terminated. */
static void clean_up_child_process (int signal_number)
{
    int status;
    wait (&status);
}

static void handle_get (int connection_fd, const char* page){
    struct server_module* module = NULL;
    if(*page =='/' && strchr(page+1,'/') == NULL){
        char buf[64];

        snprintf(buf,sizeof(buf),"%s.so",page+1);
        module = module_open(buf);
    }
    if(!module){
        // request url error
        char response[1024];
        snprintf(response,sizeof(response),not_found_response_template,page);
        write(connection_fd,response,strlen(response));
        return;
    }

    write(connection_fd,ok_response,strlen(ok_response));
    (module->generate_function)(connection_fd);
    module_close(module);
}

static void handle_connection (int connection_fd){
    char buf[1024];
    
    ssize_t byte_read = read(connection_fd,buf,sizeof(buf)-1);
    if(byte_read == 0){
        // eof 
        return;
    }
    if(byte_read < 0) {
        system_error("read");
    }
    
    char method[BUFFER_SIZE];
    char url[BUFFER_SIZE];
    char protocol[BUFFER_SIZE];
    buf[byte_read] = '\0';
    sscanf(buf,"%s %s %s",method,url,protocol);
    while(strstr(buf,"\r\n\r\n") == NULL)
        byte_read = read(connection_fd,buf,sizeof(buf) - 1);
    if(byte_read == -1){
        close(connection_fd);
        return;
    }
    if(strcmp(protocol,"HTTP/1.0") && strcmp(protocol,"HTTP/1.1")){
        // not supported protocol
        write(connection_fd,bad_request_response,strlen(bad_request_response));
        return;
    }
    if(strcmp(method,"GET")){
        char response[1024];
        snprintf (response, sizeof (response),
        bad_method_response_template, method);
        write (connection_fd, response, strlen (response));
        return;
    }

    handle_get(connection_fd,url);

}



void server_run (struct in_addr local_address, uint16_t port){

    struct sockaddr_in socket_address;
    int rval;
    struct sigaction sigchld_action;

    int server_socket;
    /* Install a handler for SIGCHLD that cleans up child processes that
    have terminated. */
    memset (&sigchld_action, 0, sizeof (sigchld_action));
    sigchld_action.sa_handler = &clean_up_child_process;
    sigaction (SIGCHLD, &sigchld_action, NULL);

    /* Create a TCP socket. */
    server_socket = socket (PF_INET, SOCK_STREAM, 0);

    int enable = 1;
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
        system_error("setsockopt(SO_REUSEADDR) failed");


    if (server_socket == -1)
    system_error ("socket");
    /* Construct a socket address structure for the local address on
    which we want to listen for connections. */
    memset (&socket_address, 0, sizeof (socket_address));
    socket_address.sin_family = AF_INET;
    socket_address.sin_port = port;
    socket_address.sin_addr = local_address;
    /* Bind the socket to that address. */
    rval = bind (server_socket, &socket_address, sizeof (socket_address));

    if (rval != 0)
        system_error ("bind");
    /* Instruct the socket to accept connections. */
    rval = listen (server_socket, 10);
    if (rval != 0)
        system_error ("listen");

    if (verbose) {
        /* In verbose mode, display the local address and port number
        we’re listening on. */
        socklen_t address_length;
        /* Find the socket’s local address. */
        address_length = sizeof (socket_address);
        rval = getsockname (server_socket, &socket_address, &address_length);
        assert (rval == 0);
        /* Print a message. The port number needs to be converted from
        network byte order (big endian) to host byte order. */
        printf ("server listening on %s:%d\n",
            inet_ntoa (socket_address.sin_addr),
            (int) ntohs (socket_address.sin_port));
    }

    for(;;){
        struct sockaddr_in remote_address;
        socklen_t address_length;
        int connection;
        pid_t child_pid;
        address_length = sizeof (remote_address);
        connection = accept(server_socket,&remote_address,&address_length);
        if (connection == -1) {
        /* The call to accept failed. */
            if (errno == EINTR){
                /* The call was interrupted by a signal. Try again. */
                continue;
            }else {
                /* Something else went wrong. */
                system_error ("accept");
            }
        }
        /* We have a connection. Print a message if we’re running in
        verbose mode. */
        if (verbose) {
            socklen_t address_length;
            /* Get the remote address of the connection. */
            address_length = sizeof (socket_address);
            rval = getpeername (connection, &socket_address, &address_length);
            assert (rval == 0);
            /* Print a message. */
            printf ("connection accepted from %s\n",
                inet_ntoa (socket_address.sin_addr));
        }
            

        /* Fork a child process to handle the connection. */
        child_pid = fork ();
        if(child_pid == 0){
            // child process
            // close(STDIN_FILENO);
            // close(STDOUT_FILENO);
            close(server_socket);
            handle_connection(connection);
            close(connection);
            exit(0);
        }else if(child_pid > 0){
            close(connection);
        }else 
            system_error("fork");
    }
}